#include <sys/time.h>
#include <time.h>
#include <stdint.h>
#include <stdarg.h>

#include "acelog.h"

#define IO_BUF_SIZE 1024
#define TOKEN_SIZE 32
#define TOKEN_VAL_SIZE 1024
#define MAXLEN_CFG_LINE 1024
#define LEVEL_CNT 5

static acelog_ctx_t *ctx = NULL;
static char readbuf[IO_BUF_SIZE];
static const char *tb_level[LEVEL_CNT] = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
static const acelog_level tb_level_enum[LEVEL_CNT] = {ACELOG_LEVEL_DEBUG, ACELOG_LEVEL_INFO, ACELOG_LEVEL_WARN,
                                  ACELOG_LEVEL_ERROR, ACELOG_LEVEL_FATAL};
static unsigned char tb_level_mask[LEVEL_CNT] = {0x1F, 0x0F, 0x07, 0x03, 0x01};
static const int tb_align[LEVEL_CNT] = {0, 1, 1, 0, 0};

static int _check_buf_size(int size, int cur)
{
    if (cur >= size) {
        fprintf(stderr, "out of token buffer\n");
        return 1;
    }
    return 0;
}

static void _init_ctx()
{
    // init ctx
    ctx->level_bitmap = tb_level_mask[3]; //ERROR lv

    ctx->out_type[0] = STDOUT;
    ctx->out_type[1] = STDOUT;
    ctx->out_type[2] = STDOUT;
    ctx->out_type[3] = STDERR;
    ctx->out_type[4] = STDERR;

    ctx->fp[0] = ctx->fp[1] = ctx->fp[2] 
               = ctx->fp[3] = ctx->fp[4] = NULL;
}

static int _update_ctx_fd(int index, output_type type, char *filepath)
{
    if (ctx->fp[index] != NULL) {
        fprintf(stderr, "repeated define in configure\n");
        return -1;
    } 

    ctx->out_type[index] = type;
    if (type == STDOUT) {
        ctx->fp[index] = stdout;
    } else if(type == STDERR) {
        ctx->fp[index] = stderr;
    } else {
        //open file, w+: create if no this file
        ctx->fp[index] = fopen(filepath, "w+");
        if (ctx->fp[index] == NULL) {
            fprintf(stderr, "Unable to open '%s': %s", filepath, strerror(errno));
            return -1;
        }
    }
    return 0;
}

static void _fill_ctx_fd()
{
    int i;
    output_type type;
    for (i = 0; i < LEVEL_CNT; i++) {
        if (ctx->fp[i] == NULL) {
            type = ctx->out_type[i];

            if (type == STDOUT) {
                ctx->fp[i] = stdout;
            } else if (type == STDERR) {
                ctx->fp[i] = stderr;
            }
        }
    }
}

/* 
 * return position of meeting ending
 *
 */
static int _line_to_token(char *buf, char *token, char ending, int start_pos, int token_size)
{
    int i = start_pos;
    int j = 0;
    while (buf[i] != '\0') {
        if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\n' || buf[i] == '\r') {
            i++;
            continue;
        }
        if (buf[i] == ending) break;
        token[j++] = buf[i];
        if (_check_buf_size(token_size, j)) return -1;
        i++;
    }
    token[j] = '\0';

    return i;
}

static int _get_level(const char *token)
{
    int i;
    for (i = 0; i < LEVEL_CNT; i++) {
        if (strcmp(token, tb_level[i]) == 0)
            break;
    }
    if (i == LEVEL_CNT) {
        fprintf(stderr, "no such level\n");
        return -1;
    }
    return i;
}

static unsigned char _get_level_make(const char *token)
{
    int index = _get_level(token);
    if (index == -1) {
        return 0;
    }
    return tb_level_mask[index];
}

static int _acelog_read_config(FILE *conf)
{
    read_conf_status status = READING_TAG;
    int i;
    int index;  // output set
    int flag_reading = 0;
    char token[TOKEN_SIZE];
    char token_val[TOKEN_VAL_SIZE];

    _init_ctx();
    // read from configure file
    while (fgets(readbuf, IO_BUF_SIZE, conf)) {
        // init inline iter
        i = 0;

        // handle comment
        if (readbuf[0] == '\0' || readbuf[0] == '#') {
            continue;
        }

        // meet tag
        if (readbuf[0] == '[') {
            status = READING_TAG;
            i++;
        }

        switch (status) {
            case READING_TAG:
                if (_line_to_token(readbuf, token, ']', i, TOKEN_SIZE) == -1) {
                    return -1;
                }
                if (strcmp(token, "formats") == 0) {
                    status = READING_FORMAT;
                } else if (strcmp(token, "level") == 0) {
                    status = READING_LEVEL;
                } else if (strcmp(token, "output") == 0) {
                    status = READING_OUTPUT;
                } else {
                    fprintf(stderr, "no such tag: '%s'\n", token);
                    return -1;
                }
                break;//case
            case READING_FORMAT:
                if (_line_to_token(readbuf, token, '\0', i, TOKEN_SIZE) == -1) {
                    return -1;
                }
                break;//case
            case READING_LEVEL:
                if (_line_to_token(readbuf, token, '\0', i, TOKEN_SIZE) == -1) {
                    return -1;
                }

                ctx->level_bitmap = _get_level_make(token);
                if (ctx->level_bitmap == 0) {
                    return -1;
                }

                break;//case
            case READING_OUTPUT:
                // read key
                i  = _line_to_token(readbuf, token, ':', i, TOKEN_SIZE);
                if (i == -1) {
                    return -1;
                }

                // read value, current readbuf[i] == ':'
                if (_line_to_token(readbuf, token_val, '\0', i+1, TOKEN_VAL_SIZE) == -1) {
                    return -1;
                }

                index = _get_level(token);
                if (index == -1) {
                    return -1;
                }

                //match value
                output_type type;
                if (strcmp(token_val, "stdout") == 0) {
                    type = STDOUT;
                } else if (strcmp(token_val, "stderr") == 0) {
                    type = STDERR;
                } else {
                    type = TOFILE;
                }

                if (_update_ctx_fd(index, type, token_val) != 0)
                    return -1;

                break;//case
        }
    }  // end read conf file
    // init those fd not configure in file
    _fill_ctx_fd();
    return 0;
}

/*
 * write buf funciton
 */
void acelog_write_time(FILE *fp)
{
    int len;
    char buf[MAXLEN_CFG_LINE];

    struct tm * timeinfo;

    //uint64_t t_usec = gettimeofday_in_usecs();  // calcs usecs since epoch
    //timeinfo = localtime(&t);
    struct timeval time_stamp;
    gettimeofday(&time_stamp, NULL);
    time_t t = time_stamp.tv_sec;
    timeinfo = localtime(&t);
    
    len = strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", timeinfo);
    fprintf(fp, "[%s.%06d] ", buf, time_stamp.tv_usec);
}

/*
 * interface
 */
int acelog_init(const char *path)
{
    FILE *conf = NULL;

    if (ctx != NULL) {
        return 0;
    }
    conf = fopen(path, "r");
    if (conf == NULL) {
        fprintf(stderr, "Unable to open '%s': %s", path, strerror(errno));
        return -1;
    }

    ctx = (acelog_ctx_t *)malloc(sizeof(acelog_ctx_t));
    if (ctx == NULL) {
        fprintf(stderr, "Unable to malloc: %s", strerror(errno));
        return -1;
    }

    if (_acelog_read_config(conf) != 0) {
        fprintf(stderr, "Unable to analyze config\n");
        return -1;
    }

    return 0;
}

int acelog_fini()
{
    int i;
    if (ctx != NULL) {
        free(ctx);
    } else {
        return -1;
    }
    for (i = 0; i < LEVEL_CNT; i++) {
        if (ctx->out_type[LEVEL_CNT] == TOFILE) {
            fclose(ctx->fp[LEVEL_CNT]);
        }
    }
    return 0;
}

void acelog(const char *file, size_t filelen, const char *func, size_t funclen, long line,
            int level, const char *format, ...)
{
    va_list args;

    if (ctx == NULL) {
        return;
    }
    if ((ctx->level_bitmap & level) == 0) {
        return;
    }
    int level_index = 0; 
    switch(level) {
        case ACELOG_LEVEL_FATAL:
            level_index = 4;
        break;
        case ACELOG_LEVEL_ERROR:
            level_index = 3;
        break;
        case ACELOG_LEVEL_WARN:
            level_index = 2;
        break;
        case ACELOG_LEVEL_INFO:
            level_index = 1;
        break;
        case ACELOG_LEVEL_DEBUG:
            level_index = 0;
        break;
    }
    // out put level
    fprintf(ctx->fp[level_index], "[%s] ", tb_level[level_index]);
    
    // align
    if (tb_align[level_index])
        fprintf(ctx->fp[level_index], " ");
     
    // out put time
    acelog_write_time(ctx->fp[level_index]);

    // out put user msg
    va_start(args, format);
    vfprintf(ctx->fp[level_index], format, args);
    va_end(args);

    // out put line number
    if (level_index == 0) {
        fprintf(ctx->fp[level_index], " (%s:%ld:%s)", file, line, func);
    }

    // out put new line
    fprintf(ctx->fp[level_index], "\n");
}
